Integration System Documentation
Overview
The ATOM SaaS platform supports 569+ integrations through a hybrid approach:
- **Native Implementations**: High-performance, first-class integrations with full feature support
- **Activepieces Universal Adapter**: Instant access to 569+ integrations without native code
---
Database Schema
OAuth States Table
Stores temporary OAuth state tokens for CSRF protection.
CREATE TABLE oauth_states (
id VARCHAR PRIMARY KEY,
tenant_id VARCHAR NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
user_id VARCHAR REFERENCES users(id) ON DELETE CASCADE,
state VARCHAR UNIQUE NOT NULL,
provider VARCHAR NOT NULL,
expires_at TIMESTAMPTZ NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW(),
redirect_uri VARCHAR,
extra_data JSON
);Integration Tokens Table
Stores OAuth credentials and API keys for all integrations.
CREATE TABLE integration_tokens (
id VARCHAR PRIMARY KEY,
tenant_id VARCHAR NOT NULL REFERENCES tenants(id) ON DELETE CASCADE,
user_id VARCHAR REFERENCES users(id) ON DELETE CASCADE,
workspace_id VARCHAR REFERENCES workspaces(id) ON DELETE SET NULL,
provider VARCHAR NOT NULL,
access_token TEXT NOT NULL,
refresh_token TEXT,
token_type VARCHAR DEFAULT 'Bearer',
expires_at TIMESTAMPTZ,
instance_url TEXT,
scope TEXT,
status VARCHAR DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(tenant_id, provider, user_id)
);---
Native Integrations
Implemented Integrations
Tier 1: Critical (Week 2-3)
| Provider | Category | Auth Type | Status |
|---|---|---|---|
| Stripe | E-commerce | OAuth2 | ✅ Complete |
| Google Drive | Productivity | OAuth2 | ✅ Complete |
| Trello | Productivity | OAuth2 | ✅ Complete |
| Asana | Productivity | OAuth2 | ✅ Complete |
| Linear | Development | OAuth2 | ✅ Complete |
| Airtable | Productivity | API Key | ✅ Complete |
Previously Implemented (13 integrations)
- Slack, Salesforce, HubSpot, Gmail, Outlook, Teams, Notion, Jira, GitHub, Shopify, Zoom, Zoho, Google Calendar
**Total Native Integrations: 19**
---
Integration Client Pattern
All native integrations follow this structure:
import { getDatabase } from '@/lib/db';
export interface {Provider}Tokens {
access_token: string;
refresh_token?: string;
// ... provider-specific fields
}
export class {Provider}Client {
private tenantId: string;
private userId?: string;
private tokens: {Provider}Tokens | null = null;
constructor(tenantId: string, userId?: string) {
this.tenantId = tenantId;
this.userId = userId;
}
// OAuth URL generation
static getAuthUrl(state: string, scopes?: string[]): string {
const config = getConfig();
const params = new URLSearchParams({
client_id: config.clientId,
redirect_uri: config.redirectUri,
response_type: 'code',
scope: scopes.join(' '),
state,
});
return `{AUTH_URL}?${params}`;
}
// Token exchange
static async exchangeCode(code: string): Promise<{Provider}Tokens> {
const config = getConfig();
const response = await fetch(`{TOKEN_URL}`, {
method: 'POST',
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: config.clientId,
client_secret: config.clientSecret,
redirect_uri: config.redirectUri,
code,
}),
});
return response.json();
}
// Load from database
async loadTokens(): Promise<boolean> {
const db = getDatabase();
const result = await db.query(
`SELECT access_token, refresh_token FROM integration_tokens
WHERE tenant_id = $1 AND provider = '{provider}'
AND status = 'active' LIMIT 1`,
[this.tenantId]
);
if (result.rows.length === 0) return false;
this.tokens = result.rows[0];
return true;
}
// Save to database
async saveTokens(tokens: {Provider}Tokens): Promise<void> {
const db = getDatabase();
await db.query(
`INSERT INTO integration_tokens (tenant_id, provider, access_token, refresh_token, status)
VALUES ($1, '{provider}', $2, $3, 'active')
ON CONFLICT (tenant_id, provider)
DO UPDATE SET access_token = EXCLUDED.access_token,
refresh_token = COALESCE(EXCLUDED.refresh_token, integration_tokens.refresh_token),
updated_at = NOW()`,
[this.tenantId, tokens.access_token, tokens.refresh_token]
);
}
// Provider-specific API methods
async getItems(): Promise<any[]> { /* ... */ }
}---
API Routes
Connect Endpoint
GET /api/integrations/{provider}?action=connect
**Response:**
{
"authUrl": "https://provider.com/oauth/authorize?...",
"state": "random_state_token"
}Callback Endpoint
GET /api/integrations/{provider}/callback?code=...&state=...
**Response:**
Redirects to /integrations?success={provider} or /integrations?error=...
Status Endpoint
GET /api/integrations/{provider}?action=status
**Response:**
{
"connected": true,
"connection": {
"id": "token_id",
"scope": "read_write",
"connected_at": "2025-02-01T00:00:00Z",
"updated_at": "2025-02-01T00:00:00Z"
}
}Disconnect Endpoint
GET /api/integrations/{provider}?action=disconnect
**Response:**
{
"success": true,
"message": "{Provider} disconnected"
}---
Activepieces Universal Adapter
For integrations without native implementation, use the Activepieces adapter:
import { createActivepiecesAdapter } from '@/lib/integrations/activepieces-adapter';
const adapter = createActivepiecesAdapter(tenantId, userId);
// Execute any piece
const result = await adapter.executePiece({
pieceId: 'discord',
action: 'send_message',
config: { webhook_url: '...' },
input: { content: 'Hello from ATOM!' },
});
// List available integrations
const pieces = adapter.listPieces('communication');
// Search integrations
const results = adapter.searchPieces('crm');Supported Categories
- Communication (Slack, Discord, Telegram, Twilio, SendGrid, ...)
- Productivity (Notion, Trello, Asana, Monday, ClickUp, ...)
- CRM (Salesforce, HubSpot, Pipedrive, Zoho, ...)
- E-commerce (Shopify, Stripe, WooCommerce, Square, ...)
- Storage (Google Drive, Dropbox, OneDrive, Box, ...)
- Development (GitHub, GitLab, Linear, Jira, ...)
- Marketing (Mailchimp, ConvertKit, ...)
- Accounting (QuickBooks, Xero, FreshBooks, ...)
- AI/ML (OpenAI, Anthropic, Hugging Face, ...)
**Total: 569+ integrations**
---
Environment Variables Pattern
All integrations follow this naming convention:
# OAuth2 Integrations
{PROVIDER}_CLIENT_ID=""
{PROVIDER}_CLIENT_SECRET=""
{PROVIDER}_REDIRECT_URI="https://your-domain.com/api/integrations/{provider}/callback"
# API Key Integrations
{PROVIDER}_API_KEY=""**Example:**
STRIPE_CLIENT_ID="ca_..."
STRIPE_CLIENT_SECRET="sk_..."
STRIPE_REDIRECT_URI="https://atom.ai/api/integrations/stripe/callback"
AIRTABLE_API_KEY="pat_..."---
Multi-Tenancy
All integration operations MUST include tenant context:
// ✅ CORRECT - Multi-tenant
const db = getDatabase();
const result = await db.query(
`SELECT * FROM integration_tokens
WHERE tenant_id = $1 AND provider = $2`,
[tenantId, provider]
);
// ❌ WRONG - Missing tenant filter
const result = await db.query(
`SELECT * FROM integration_tokens
WHERE provider = $1`,
[provider]
);Row-Level Security (RLS) is enabled on both oauth_states and integration_tokens tables.
---
Token Refresh Automation
OAuth2 access tokens are automatically refreshed:
private async request<T>(endpoint: string): Promise<T> {
// Load tokens
if (!this.tokens) await this.loadTokens();
// Check if expired
if (this.tokens.expires_at) {
const expiresAt = new Date(this.tokens.expires_at);
if (Date.now() >= expiresAt.getTime() && this.tokens.refresh_token) {
// Refresh token
this.tokens = await {Provider}Client.refreshAccessToken(this.tokens.refresh_token);
await this.saveTokens(this.tokens);
}
}
// Make request
return fetch(url, {
headers: { Authorization: `Bearer ${this.tokens.access_token}` },
});
}---
Code Generator
Generate new integrations automatically:
npm run generate:integration -- --provider stripe --auth oauth2
npm run generate:integration -- --provider airtable --auth api_key
npm run generate:integration -- --provider figma --auth basicThe generator creates:
- Client library:
src/lib/integrations/{provider}.ts - API route:
src/app/api/integrations/{provider}/route.ts - Callback route:
src/app/api/integrations/{provider}/callback/route.ts - Environment variables: Updates
.env.example
---
Testing
Test OAuth Flow
# 1. Start dev server
npm run dev
# 2. Navigate to /integrations
# 3. Click "Connect {Provider}"
# 4. Complete OAuth flow in provider popup
# 5. Verify redirect with ?success={provider}
# 6. Check database
psql $DATABASE_URL -c "SELECT * FROM integration_tokens WHERE provider = '{provider}'"Test API Call
const client = new StripeClient(tenantId);
await client.loadTokens();
const balance = await client.getBalance();
console.log(balance);---
Security
CSRF Protection
All OAuth flows use state tokens stored in oauth_states table with 10-minute expiration.
Token Encryption
Access and refresh tokens should be encrypted at rest (implementation needed).
RLS Policies
Row-Level Security ensures tenant isolation:
CREATE POLICY integration_tokens_tenant_isolation
ON integration_tokens
FOR ALL
USING (tenant_id = current_setting('app.current_tenant_id')::UUID);---
Troubleshooting
Issue: "Invalid state" error
**Cause:** OAuth state expired or invalid
**Fix:** Check oauth_states table, ensure state was created within 10 minutes
Issue: "Token exchange failed"
**Cause:** Invalid client credentials or callback URL mismatch
**Fix:** Verify CLIENT_ID, CLIENT_SECRET, and REDIRECT_URI match provider app settings
Issue: "Provider not connected"
**Cause:** No active tokens in database
**Fix:** Check integration_tokens table for provider and tenant
---
Roadmap
Week 1: Foundation ✅
- [x] Database migration
- [x] SQLAlchemy models
- [x] Code generator
Week 2-3: Tier 1 ✅
- [x] Stripe
- [x] Google Drive
- [x] Trello
- [x] Asana
- [x] Linear
- [x] Airtable
Week 4-5: Tier 2 (Planned)
- [ ] Monday.com
- [ ] ClickUp
- [ ] Dropbox
- [ ] OneDrive
- [ ] QuickBooks
- [ ] Xero
- [ ] Pipedrive
Week 6-7: Tier 3 (Planned)
- [ ] Discord
- [ ] Twilio
- [ ] Intercom
- [ ] SendGrid
- [ ] Figma
- [ ] Box
- [ ] Square
- [ ] Plaid
Week 8-10: Activepieces (Complete)
- [x] Universal adapter
- [ ] Catalog UI
- [ ] Dynamic execution engine
---
Integration Status Dashboard
To view integration status:
GET /api/integrations/status**Response:**
{
"total_integrations": 569,
"native_implementations": 19,
"connected": 8,
"categories": 9,
"providers": [
{
"id": "slack",
"name": "Slack",
"connected": true,
"status": "active"
},
// ...
]
}---
Contributing
To add a new integration:
- **Use the generator:**
- **Add to catalog:**
- **Update .env.example:**
- **Test:**
- **Document:**
---
Support
For integration-specific questions:
- **Slack**: https://api.slack.com/docs
- **Stripe**: https://stripe.com/docs/api
- **Activepieces**: https://www.activepieces.com/docs
For platform issues:
- Check logs:
tail -f backend-saas/logs/app.log - Database: Verify RLS policies are active
- Environment: Confirm all variables are set